﻿/**
 * \file Ville.cs
 * \brief Classe Ville
 * \author ARTUR Vincent
 * \author LAPERDRIX Pierre
 * \version 1.0
 */
namespace Implem
{
	using Interfaces;
	using System;
	using System.Collections.Generic;
	using System.Linq;
	using System.Text;
    using System.IO;

    /**
     * \class Ville
     * \brief Objet representant une ville
     */
	public class Ville : IVille
	{
        /*!< Nom de la ville */
		private string _nom;

        /*!< Propriete accedant a l'attribut _nom */
        public string Nom
		{
            get { return _nom; }
            set { _nom = value; }
		}

        /*!< Nombre de cases sur lequel s'etend la ville */
        private int _nbCases;

        /*!< Propriete accedant a l'attribut _nbCases */
        public int NbCases
        {
            get { return _nbCases; }
            set { _nbCases = value; }
        }

        /*!< Nombre de cases maximum sur lequel la ville peut s'etendre */
        private int _nbCasesMax;

        /*!< Propriete accedant a l'attribut _nbCasesMax */
        public int NbCasesMax
        {
            get { return _nbCasesMax; }
            set { _nbCasesMax = value; }
        }

        /*!< Matrice permettant de savoir si la ville peut s'etendre sur une case particuliere (True - peut s'etendre, False - ne peut pas s'etendre) */
        private bool[][] _expansion;

        /*!< Propriete accedant a l'attribut _expansion */
        public bool[][] Expansion
        {
            get { return _expansion; }
            set { _expansion = value; }
        }

        public int[][] VilleExt
        {
            get;
            set;
        }

        /*!< Nombre d'habitants de la ville */
		private int _pop;

        /*!< Propriete accedant a l'attribut _pop */
        public int Pop
		{
            get { return _pop; }
            set { _pop = value; }
		}

        /*!< Quantite de minerai produite dans la ville a chaque tour */
		private int _mineraiParTour;

        /*!< Propriete accedant a l'attribut _mineraiParTour */
        public int MineraiParTour
		{
            get { return _mineraiParTour; }
            set { _mineraiParTour = value; }
		}

        /*!< Quantite de nourriture produite dans la ville a chaque tour*/
		private int _nourritureParTour;

        /*!< Propriete accedant a l'attribut _nourritureParTour */
        public int NourritureParTour
		{
            get { return _nourritureParTour; }
            set { _nourritureParTour = value; }
		}

        /*!< Cap de nourriture a atteindre pour que la population de la ville augmente */
		private int _capNourriture;

        /*!< Propriete accedant a l'attribut _capNourriture */
        public int CapNourriture
		{
            get { return _capNourriture; }
            set { _capNourriture = value; }
		}

        /*!< Quantite de nourriture non encore utilisee pour l'augmentation de la population */
		private int _stockNourriture;

        /*!< Propriete accedant a l'attribut _stockNourriture */
        public int StockNourriture
		{
            get { return _stockNourriture; }
            set { _stockNourriture = value; }
		}

        /*!< Cap de minerai a atteindre pour produire l'unite souhaitee par le joueur */
        private int _capMinerai;

        /*!< Propriete accedant a l'attribut _capMinerai */
        public int CapMinerai
        {
            get { return _capMinerai; }
            set { _capMinerai = value; }
        }

        /*!<  Quantite de minerai non encore utilisee pour la production d'une unite*/
        private int _stockMinerai;

        /*!< Propriete accedant a l'attribut _stockMinerai */
        public int StockMinerai
        {
            get { return _stockMinerai; }
            set { _stockMinerai = value; }
        }

        /*!< Type d'unite dont la production est en cours*/
		private typeProduction _uniteEnCours;

        /*!< Propriete accedant a l'attribut _uniteEnCours */
        public typeProduction UniteEnCours
		{
            get { return _uniteEnCours; }
            set { _uniteEnCours = value; }
		}

        /**!< Constantes pour la sauvegarde */
        private const char AUCUNE = 'a';
        private const char DIRECTEUR = 'd';
        private const char ETUDIANT = 'e';
        private const char ENSEIGNANT = 't';

        /*!< Joueur proprietaire de la ville */
		private Joueur _joueurProprietaire;

        /*!< Propriete accedant a l'attribut _joueurProprietaire */
        public Joueur JoueurProprietaire
		{
            get { return _joueurProprietaire; }
            set { _joueurProprietaire = value; }
		}
        
        /*!< Position centrale de la ville sur la carte */
        private Position _positCentrale;

        /*!< Propriete accedant a l'attribut _positCentrale */
        public Position PositCentrale
		{
            get { return _positCentrale; }
            set { _positCentrale = value; }
		}


        /*!< Events permettant d'informer l'observateur des modifications du modele */
        public event EventHandler<ExpansionVilleEventArgs> ExpansionEvent;

        /*!< Classe permettant de renvoyer la position de l'expansion de la ville a l'observateur*/
        public class ExpansionVilleEventArgs : EventArgs
        {
            public Position pos { get; set; }
        }

        /*!< Events permettant d'informer l'observateur d'une production d'unite */
        public event EventHandler<UniteProduiteEventArgs> ProductionEvent;

        /*!< Classe permettant de renvoyer le type de l'unite a produite a l'observateur*/
        public class UniteProduiteEventArgs : EventArgs
        {
            public IUnite unite { get; set; }
        }

        /*!< Events permettant d'informer l'observateur du changement du Joueur Propriétaire */
        public event EventHandler<PriseEventArgs> PriseEvent;

        /*!< Classe permettant d'envoyer la commande de prise de la ville*/
        public class PriseEventArgs : EventArgs
        {
            public Joueur ancienProp { get; set;}
            public Joueur nouveauProp { get; set; }
        }

        /*!< Events permettant d'informer l'observateur de la destruction de la ville */
        public event EventHandler<DestructionnEventArgs> DestructionnEvent;

        /*!< Classe permettant d'envoyer la commande de destruction de la ville*/
        public class DestructionnEventArgs : EventArgs
        {
        }




        /**
         * \fn public Ville(Joueur j,Position p,String s)
         * \brief Constructeur de Ville
         * \param j le joueur proprietaire
         * \param p la position centrale de la ville
         * \param s le nom de la ville
         */
        public Ville(Joueur j, Position p)
        {
            this._joueurProprietaire = j;

            this._nbCases = 1;

            this._positCentrale = p;

            this._pop = 1;

            this._capNourriture = 10;

            this._capMinerai = 0;

            this._uniteEnCours = typeProduction.aucune;

            this._stockNourriture = 0;

            this._stockMinerai = 0;

            this._nbCasesMax = calculNbMax();

            this.VilleExt = new int[5][];
            for (int i = 0; i < 5; i++)
            {
                VilleExt[i] = new int[5];
                for (int k = 0; k < 5; k++)
                {
                    VilleExt[i][k] = 0;
                }
            }

            Case tmp = Carte.getCase(p);
            this._mineraiParTour = tmp.Minerai;
            this._nourritureParTour = tmp.Nourriture;
        }

        /**
         * \fn public Ville(Joueur j,Position p,String s)
         * \brief Calcul le nombre de cases Max et reserve les cases sur la carte pour sa future expansion
         * \return le nombre de cases maximum sur lequel la ville peut s'agrandir
         */
        public int calculNbMax()
        {
            int nbCases = 0;
            Expansion = new bool[5][];
            for (int k = 0; k < 5; k++)
            {
                Expansion[k] = new bool[5];
            }

            for (int i = -2; i < 3; i++)
            {
                for (int j = -2; j < 3; j++)
                {
                    int x = this.PositCentrale.X+i;
                    int y = this.PositCentrale.Y+j;
                    Position p = new Position(x,y);
                    if (x >= 0 && x < Carte.Strat.Taille && y >= 0 && y < Carte.Strat.Taille && !Carte.Villes.ContainsKey(p))
                    {
                        nbCases++;
                        Carte.Villes.Add(p,this);
                        Expansion[i+2][j+2] = true;
                    }
                    else
                    {
                        Expansion[i+2][j+2] = false;
                    }
                }
            }
            return nbCases;
        }

        /**
         * \fn public virtual void augmentationPop()
         * \brief Augmente la population de la ville
         */
        public virtual void augmentationPop()
		{
            StockNourriture = 0;
            CapNourriture = (int) (1.5*CapNourriture);
            Pop++;
            if (NbCases < NbCasesMax)
            {
                expansionVille();
            }
		}

        /**
         * \fn public virtual void finTour()
         * \brief Execute les actions de fin de tour
         */
		public virtual void finTour()
		{
            //Augmentation stock de nourriture
            StockNourriture += NourritureParTour;
            if (StockNourriture > CapNourriture)
            {
                augmentationPop();
            }

            //Augmentation stock de minerai
            StockMinerai += MineraiParTour;
            if (StockMinerai > CapMinerai && this.UniteEnCours != typeProduction.aucune)
            {
                production();
            }
		}

        /**
         * \fn public virtual void expansionVille()
         * \brief Etend la ville sur une case adjacente
         */
        public virtual void expansionVille()
        {
            Random rand = new Random();
            bool ajoute = false;
            while (!ajoute)
            {
                int xDif = rand.Next(-2, 3);
                int yDif = rand.Next(-2, 3);
                if (xDif != 0 || yDif != 0)
                {
                    int x = PositCentrale.X + xDif;
                    int y = PositCentrale.Y + yDif;
                    Position p = new Position(x, y);
                    if (x >= 0 && x < Carte.Strat.Taille && y >= 0 && y < Carte.Strat.Taille && Expansion[xDif+2][yDif+2])
                    {

                        //Augmentation des ressources par tour de la ville
                        NourritureParTour += Carte.getCase(p).Nourriture;
                        MineraiParTour += Carte.getCase(p).Minerai;

                        //Ajout de la presence de la case dans la matrice expansion
                        Expansion[xDif + 2][yDif + 2] = false;
                        VilleExt[xDif + 2][yDif + 2] = 1;

                        //Augmentation du nombre de cases
                        NbCases++;

                        //Case ajoutee, on sort de la boucle
                        ajoute = true;

                        //Envoi a l'observateur de la modification
                        EventHandler<ExpansionVilleEventArgs> handler = ExpansionEvent;
                        //Creation d'un EventArgs
                        ExpansionVilleEventArgs args = new ExpansionVilleEventArgs();
                        args.pos = p;

                        if (handler != null)
                        {
                            handler(this, args);
                        }

                    }
                }
            }
            
        }

        /**
         * \fn public virtual void setProduction(typeProduction tp)
         * \brief Definit le type d'unite produite par la ville
         * \param tp le type de production souhaite
         */
        public virtual void setProduction(typeProduction tp)
        {
            this.UniteEnCours = tp;
            switch (tp)
            {
                case typeProduction.directeur:
                    this.CapMinerai = 200;
                    break;
                case typeProduction.etudiant:
                    this.CapMinerai = 100;
                    break;
                case typeProduction.enseignant:
                    this.CapMinerai = 60;
                    break;
                default:
                    this.CapMinerai = 0;
                    break;
            }
        }

        /**
         * \fn public virtual void production()
         * \brief Produit l'unite dont le type est defini dans l'attribut _uniteEnCours
         */
        public virtual void production()
        {
            //Production de l'unite
            IUnite u = JoueurProprietaire.produireUnite(this.UniteEnCours,this.PositCentrale);

            //Envoi a l'observateur de la modification
            EventHandler<UniteProduiteEventArgs> handler = ProductionEvent;
            //Creation d'un EventArgs
            UniteProduiteEventArgs args = new UniteProduiteEventArgs();
            args.unite = u;

            if (handler != null)
            {
                handler(this, args);
            }

            //Diminution du stock de minerai apres production de l'unite
            this.StockMinerai -= CapMinerai;

            //Remise a zero 
            this.setProduction(typeProduction.aucune);

        }

        /**
         * \fn public void priseVille()
         * \brief Prise de la ville par un joueur adverse
         */
        public void priseVille(Joueur ancien, Joueur nouveau)
        {
            //Mise a zero de la production
            this.setProduction(typeProduction.aucune);

            //Envoi a la vue du changement de proprietaire
            EventHandler<PriseEventArgs> handler = PriseEvent;
            //Creation d'un EventArgs
            PriseEventArgs args = new PriseEventArgs();
            args.ancienProp = ancien;
            args.nouveauProp = nouveau;

            if (handler != null)
            {
                handler(this, args);
            }
        }


        /**
         * \fn public void save(StreamWriter sw)
         * \brief Sauvegarde la ville
         * \param sw le flux dans lequel ecrire
         */
        public void save(StreamWriter sw)
        {
            sw.WriteLine(Nom);
            sw.WriteLine(NbCases);
            sw.WriteLine(NbCasesMax);
            sw.WriteLine(Pop);
            sw.WriteLine(MineraiParTour);
            sw.WriteLine(CapMinerai);
            sw.WriteLine(NourritureParTour);
            sw.WriteLine(CapNourriture);
            switch (UniteEnCours)
            {
                case typeProduction.directeur:
                    sw.WriteLine(DIRECTEUR);break;
                case typeProduction.enseignant:
                    sw.WriteLine(ENSEIGNANT);break;
                case typeProduction.etudiant:
                    sw.WriteLine(ETUDIANT);break;
                default:
                    sw.WriteLine(AUCUNE);break;
            }

            sw.WriteLine(PositCentrale.X);
            sw.WriteLine(PositCentrale.Y);

            for (int i = 0; i < 5; i++)
            {
                for (int j = 0; j < 5; j++)
                {
                    sw.Write((Expansion[i][j]) ? '1' : '0');
                    sw.Write(VilleExt[i][j]);
                }//for j
            }//for i
            sw.WriteLine();
        }//save


        /**
         * \fn public void load(StreamReader sr,Joueur j)
         * \brief Charge la ville
         * \param sr le flux dans lequel lire
         * \param j le joueur proprietaire de la ville
         */
        public void load(StreamReader sr, Joueur j)
        {
            JoueurProprietaire = j;
            Nom = sr.ReadLine();
            NbCases = Convert.ToInt32(sr.ReadLine());
            NbCasesMax = Convert.ToInt32(sr.ReadLine());
            Pop = Convert.ToInt32(sr.ReadLine());
            MineraiParTour = Convert.ToInt32(sr.ReadLine());
            CapMinerai = Convert.ToInt32(sr.ReadLine());
            NourritureParTour = Convert.ToInt32(sr.ReadLine());
            CapNourriture = Convert.ToInt32(sr.ReadLine());
            char c = (char)sr.Read();
            switch (c)
            {
                case DIRECTEUR:
                    UniteEnCours = typeProduction.directeur;break;
                case ETUDIANT:
                    UniteEnCours = typeProduction.etudiant;break;
                case ENSEIGNANT:
                    UniteEnCours = typeProduction.enseignant;break;
                default:
                    UniteEnCours = typeProduction.aucune;break;
            }
            sr.ReadLine();
            int x = Convert.ToInt32(sr.ReadLine());
            int y = Convert.ToInt32(sr.ReadLine());
            PositCentrale = new Position(x, y);
            for (int i = 0; i < 5; i++)
            {
                for (int k = 0; k < 5; k++)
                {
                    Expansion[i][k] = (((char)sr.Read() == '0') ? false : true);
                    VilleExt[i][k] = (((char)sr.Read() == '0') ? 0 : 1);
                }//for k
                Console.WriteLine();
            }//for i
            sr.ReadLine();
        }


	}
}

